/*
 * Copyright (C) 2010 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.binding.mapping;

import java.util.*;
import org.jdesktop.beansbinding.*;
import org.jdesktop.observablecollections.*;

/**
 * Validation extension for a {@link MappingGroup}.<br>
 * Use it to validate properties not directly mapped to a widget or to validate {@link List} constraints.
 * 
 * @param <T> The model type
 * @author M. Hautle
 * @since 10.12.2010
 */
public class ModelValidator<T> {
    /** The properties to validate. */
    private final List<Listener> properties = new ArrayList<Listener>();

    /** The mapping to inform */
    private final MappingGroup<T> mapping;

    /** The model or null. */
    private T model;

    /**
     * Default constructor.
     * 
     * @param mapping The mapping to inform
     */
    public ModelValidator(MappingGroup<T> mapping) {
        this.mapping = mapping;
    }

    /**
     * Sets the model to observe.
     * 
     * @param model The model or null
     */
    public void setModel(T model) {
        // unregister old model
        if (this.model != null) {
            for (Listener l : properties)
                l.unsetModel(this.model);
        }
        this.model = model;
        // register new model
        if (this.model != null)
            for (Listener l : properties)
                l.setModel(this.model);
    }

    /**
     * Adds the given property to the list of properties to observe.
     * 
     * @param el The el expression
     */
    public void addProperty(String el) {
        properties.add(new Listener(el));
    }

    /**
     * Validates all registered properties.
     */
    public void validateAllProperties() {
        if (model == null)
            return;
        for (Listener l : properties)
            validate(l.el);
    }

    /**
     * Triggers the validation of the given property.
     * 
     * @param el The el expression
     */
    private void validate(String el) {
        mapping.validateProperty(el);
    }

    /**
     * Listener for a property.
     * 
     * @author M. Hautle
     * @since 11.12.2010
     */
    private class Listener implements PropertyStateListener, ObservableListListener {
        /** The simple el expression. */
        final String el;

        /** The property access. */
        private final BeanProperty<T, Object> property;

        /**
         * Default constructor.
         * 
         * @param el The simple el expression
         */
        public Listener(String el) {
            this.el = el;
            property = BeanProperty.<T, Object> create(el);
        }

        /**
         * Sets the model to listen.
         * 
         * @param model The model to set
         */
        public void setModel(T model) {
            property.addPropertyStateListener(model, this);
            registerList(property.getValue(model));
        }

        /**
         * Removes the listener from the given model.
         * 
         * @param model The old model
         */
        public void unsetModel(T model) {
            property.removePropertyStateListener(model, this);
            if (property.isReadable(model))
                unregisterList(property.getValue(model));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void propertyStateChanged(PropertyStateEvent pse) {
            ModelValidator.this.validate(el);
            if (!pse.getValueChanged())
                return;
            unregisterList(pse.getOldValue());
            registerList(pse.getNewValue());
        }

        /**
         * Registers this {@link ObservableListListener} if the element is a {@link ObservableList}.
         * 
         * @param val A value or null
         */
        private void registerList(final Object val) {
            if (val instanceof ObservableList)
                ((ObservableList) val).addObservableListListener(this);
        }

        /**
         * Unregisters this {@link ObservableListListener} if the element is a {@link ObservableList}.
         * 
         * @param val A value or null
         */
        private void unregisterList(final Object val) {
            if (val instanceof ObservableList)
                ((ObservableList) val).removeObservableListListener(this);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void listElementsAdded(ObservableList list, int index, int length) {
            ModelValidator.this.validate(el);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void listElementsRemoved(ObservableList list, int index, List oldElements) {
            ModelValidator.this.validate(el);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void listElementReplaced(ObservableList list, int index, Object oldElement) {
            ModelValidator.this.validate(el);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void listElementPropertyChanged(ObservableList list, int index) {
            ModelValidator.this.validate(el);
        }
    }
}
